package org.dromara.system.kernel;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.utils.BeanUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.redis.utils.RedisUtils;
import org.dromara.system.constants.OrderConstants;
import org.dromara.system.domain.*;
import org.dromara.system.domain.bo.PayGroupAccountQueryBo;
import org.dromara.system.domain.bo.PayGroupTypeBo;
import org.dromara.system.domain.bo.PayGroupTypeQuery2Bo;
import org.dromara.system.domain.bo.aparams.AOrderBo;
import org.dromara.system.domain.vo.PayGroupTypeVo;
import org.dromara.system.domain.vo.PayOrderInsertAccountVo;
import org.dromara.system.domain.vo.PayPolicyVo;
import org.dromara.system.domain.vo.PayTypeOut2Vo;
import org.dromara.system.mapper.PayGroupAccountMapper;
import org.dromara.system.mapper.PayGroupTypeMapper;
import org.dromara.system.mapper.PayPolicyMapper;
import org.dromara.system.mapper.PayTypeMapper;
import org.dromara.system.service.IPayAccountService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;

@RefreshScope
@Service
@RequiredArgsConstructor
public class PayGroupKernel {

    private final PayAppKernel payAppKernel;
    private final PayGroupTypeMapper groupTypeMapper;
    private final PayPolicyKernel payPolicyKernel;
    private final PayPolicyMapper policyMapper;
    private final PayGroupAccountMapper payGroupAccountMapper;
    private final PayTypeMapper payTypeMapper;
    private final RedisAlgorithmKernel redisAlgorithmKernel;
    private final IPayAccountService accountService;
    @Value("${fixedKeyTime}")
    public Integer fixedKeyTime;

    /**
     * 获取应用配置的支付平台
     *
     * @param bo 轮询收款账号参数
     * @return 支付平台列表
     */
    public R<List<PayTypeOut2Vo>> selectPayGroupType(PayGroupTypeQuery2Bo bo) {
        //查询应用
        R<PayApp> rApp = payAppKernel.getPayApp(bo.getAppId(), bo.getSecretKey());
        if (R.isError(rApp)) {
            return R.fail(rApp.getMsg());
        }
        PayApp app = rApp.getData();

        //查询轮询组平台
        LambdaQueryWrapper<PayGroupType> query = new LambdaQueryWrapper<>();
        query.eq(PayGroupType::getGroupId, app.getGroupId());
        query.orderByDesc(PayGroupType::getSort);
        List<PayGroupTypeVo> typeVos = groupTypeMapper.selectVoList(query, PayGroupTypeVo.class);
        if (typeVos.isEmpty()) {
            return R.fail("no config payType");
        }

        //查询风控策略
        Map<Long, PayPolicyVo> policyMap = new HashMap<>();
        Set<Long> policyIds = typeVos.stream().map(PayGroupTypeVo::getPolicyId).collect(Collectors.toSet());
        if (!policyIds.isEmpty() && StringUtils.isNotEmpty(bo.getCountry())) {
            policyMapper.selectVoBatchIds(policyIds).forEach(policy -> policyMap.put(policy.getPolicyId(), policy));
        }

        //风险策略 国家校验
        if (!policyMap.isEmpty()) {
            typeVos = typeVos.stream().filter(typeVo -> {
                PayPolicyVo policy = policyMap.get(typeVo.getPolicyId());
                if (policy == null) {
                    return true;
                }
                return R.isSuccess(payPolicyKernel.validateCountry(policy, bo.getCountry()));
            }).toList();
        }
        if (typeVos.isEmpty()) {
            return R.fail("No payment methods available");
        }

        //查询轮询组收款账号
        LambdaQueryWrapper<PayGroupAccount> query1 = new LambdaQueryWrapper<>();
        query1.eq(PayGroupAccount::getGroupId, app.getGroupId());//该轮询组
        query1.in(PayGroupAccount::getStatus, 1);//在线状态/下线
        List<PayGroupAccount> groupAccounts = payGroupAccountMapper.selectList(query1);
        if (groupAccounts.isEmpty()) {
            return R.fail("no online account");
        }
        Map<Long, List<PayGroupAccount>> accountMap = groupAccounts.stream().collect(Collectors.groupingBy(PayGroupAccount::getGroupTypeId));

        //查询平台配置
        Set<Long> typeIds = typeVos.stream().map(PayGroupTypeVo::getTypeId).collect(Collectors.toSet());
        List<PayType> types = payTypeMapper.selectBatchIds(typeIds);
        Map<Long, PayType> typeMap = types.stream().collect(Collectors.toMap(PayType::getTypeId, payType -> payType));

        //处理轮询逻辑
        List<PayTypeOut2Vo> outs = new ArrayList<>();
        typeVos.forEach(groupType -> {
            //获取账号平台配置
            PayType type = typeMap.get(groupType.getTypeId());
            if (type == null) {
                return;
            }

            //此平台轮询组账号
            List<PayGroupAccount> accounts = accountMap.get(groupType.getId());
            if (accounts == null || accounts.isEmpty()) {
                return;
            }
            //输出对象
            PayTypeOut2Vo out = BeanUtils.copy(groupType, PayTypeOut2Vo.class);
            if (out.getDescribes() == null) {
                out.setDescribes("");
            }
            if (out.getLogo() == null) {
                out.setLogo("");
            }
            outs.add(out);
        });

        if (outs.isEmpty()) {
            return R.fail("no account available");
        }
        return R.ok(outs);
    }


    /**
     * 轮询收款账号
     *
     * @param bo          轮询收款账号参数
     * @return 支付平台列表
     */
    public R<PayOrderInsertAccountVo> loopPayGroupAccount(AOrderBo bo, PayApp app) {
        return loopPayGroupAccount(new PayGroupAccountQueryBo(app.getGroupId(), bo.getPayType(), bo.getUsdMoney(), bo.getBilling().getCountry(), bo.getBilling().getEmail()));
    }
    public R<PayOrderInsertAccountVo> loopPayGroupAccount(PayGroupAccountQueryBo bo) {

        //查询轮询组平台
        LambdaQueryWrapper<PayGroupType> query = new LambdaQueryWrapper<>();
        query.eq(PayGroupType::getGroupId, bo.getGroupId());
        query.eq(PayGroupType::getCode, bo.getPayTypeCode());
        query.orderByDesc(PayGroupType::getSort);
        List<PayGroupTypeVo> typeVos = groupTypeMapper.selectVoList(query, PayGroupTypeVo.class);
        if (typeVos.isEmpty()) {
            return R.fail("no config payType");
        }

        PayGroupTypeVo groupType = typeVos.get(0);

        //查询风控策略
        if (groupType.getPolicyId() != null && StringUtils.isNotEmpty(bo.getCountry())) {
            List<PayPolicyVo> pols = policyMapper.selectVoBatchIds(Collections.singletonList(groupType.getPolicyId()));
            if (!pols.isEmpty()) {
                PayPolicyVo policy = pols.get(0);
                R<String> r = payPolicyKernel.validateCountry(policy, bo.getCountry());
                if (R.isError(r)) {
                    return R.fail(r.getMsg());
                }
            }
        }

        //查询轮询组收款账号
        LambdaQueryWrapper<PayGroupAccount> query1 = new LambdaQueryWrapper<>();
        query1.eq(PayGroupAccount::getGroupId, groupType.getGroupId());//该轮询组
        query1.eq(PayGroupAccount::getGroupTypeId, groupType.getId());//该平台
        query1.in(PayGroupAccount::getStatus, 1, 2);//在线状态/下线
        List<PayGroupAccount> accounts = payGroupAccountMapper.selectList(query1);
        if (accounts.isEmpty()) {
            return R.fail("There are no available accounts for this payment method");//此支付方式没有可用账号
        }

        boolean hasEmail = StringUtils.isNotEmpty(bo.getEmail());//是否传了邮箱
        boolean isFixed = Objects.equals(groupType.getIsFixed(), 1) && Objects.equals(groupType.getIsLoop(), 1);//是否开启防止关联
        String fixedKey = OrderConstants.pay_fixed_account + groupType.getId() + ":" + bo.getEmail();

        //防止关联
        PayGroupAccount groupAccount = null;
        boolean isOldAct = false;//是否防关联账号
        if (hasEmail && isFixed && RedisUtils.hasKey(fixedKey)) {
            //邮箱绑定收款平台的一个收款账号，缓存一小时
            Object groupActId = RedisUtils.getCacheObject(fixedKey);
            List<PayGroupAccount> tmpActs = accounts.stream().filter(tmp -> Objects.equals(tmp.getId(), groupActId)).toList();
            if (!tmpActs.isEmpty()) {
                //构建收款账号输出对象
                groupAccount = tmpActs.get(0);
                isOldAct = true;
            }
        }

        if(groupAccount == null) {
            //在线账号
            accounts = accounts.stream().filter(tmp -> tmp.getStatus() == 1).toList();
            if (accounts.isEmpty()) {
                return R.fail("no online account");
            }
            //轮询获取收款账号
            groupAccount = accountLoop(groupType, accounts, bo.getUsdMoney());
            if(groupAccount == null) {
                return R.fail("no available account");//没有可用账号
            }
            //防止关联
            if (hasEmail && isFixed) {
                //邮箱绑定收款平台的一个收款账号，缓存一小时
                RedisUtils.setCacheObject(fixedKey, groupAccount.getId(), Duration.ofHours(fixedKeyTime));
            }
        }

        //收款账号
        PayAccount account = accountService.selectById(groupAccount.getAccountId());
        if (account == null) {
            return R.fail("Account is invalid");
        }
        if (isOldAct) {
            //如果是防关联账号, 则只校验账号是否禁用
            if (Objects.equals(account.getStatus(), 3)) {
                return R.fail("Account is disabled");
            }
        } else {
            //校验账号是否在线
            if (!Objects.equals(account.getStatus(), 1)) {
                return R.fail("Account is down");
            }
        }

        //获取支付平台
        PayType payType = payTypeMapper.selectById(account.getPayTypeId());
        if (payType == null) {
            return R.fail("PayType is invalid");//支付平台不可用
        }

        //组装数据
        PayOrderInsertAccountVo vo = new PayOrderInsertAccountVo(groupType, groupAccount, account, payType);
        if(isFixed) {
            vo.setFixedKey(fixedKey);
        }
        return R.ok(vo);
    }


    /**
     * 轮询获取收款账号
     *
     * @param groupType 轮询组配置
     * @param accounts  轮询组账号列表
     * @param money     收款金额
     * @return 账号
     */
    private PayGroupAccount accountLoop(PayGroupTypeVo groupType, List<PayGroupAccount> accounts, BigDecimal money) {

        //循环收单
        Map<Long, Integer> loops = new HashMap<>();
        if (Objects.equals(groupType.getIsLoop(), 1)) {
            loops = redisAlgorithmKernel.getLoopAccount(groupType.getId());
        }

        PayGroupAccount groupAct = null;
        accounts = accounts.stream().peek(tmp -> {
            if (tmp.getSort() == null) {
                tmp.setSort(0);
            }
        }).sorted(Comparator.comparing(PayGroupAccount::getSort).reversed()).toList();//排序 倒叙
        for (PayGroupAccount obj : accounts) {
            //循环收单，收款账号已使用过 并且 收单次数大于等于限制次数
            if (!loops.isEmpty() && loops.containsKey(obj.getAccountId()) && loops.get(obj.getAccountId()) >= groupType.getNextCount()) {
                continue;
            }

            //检查是否小于单笔订单限额
            if (money != null && money.compareTo(BigDecimal.ZERO) > 0
                && obj.getMinMoney() != null && obj.getMinMoney().compareTo(BigDecimal.ZERO) > 0
                && money.compareTo(obj.getMinMoney()) < 0) {
                continue;
            }

            //检查是否大于单笔订单限额
            if (money != null && money.compareTo(BigDecimal.ZERO) > 0
                && obj.getMaxMoney() != null && obj.getMaxMoney().compareTo(BigDecimal.ZERO) > 0
                && money.compareTo(obj.getMaxMoney()) > 0) {
                continue;
            }

            groupAct = obj;
            break;
        }

        if (!loops.isEmpty() && groupAct == null) {
            redisAlgorithmKernel.rmGroupLoop(groupType.getId());
            return accountLoop(groupType, accounts, money);
        }

        if (groupAct == null) {
            return null;
        }

        //记录本次收款账号
        int num = 1;
        if (loops.containsKey(groupAct.getAccountId())) {
            num = loops.get(groupAct.getAccountId()) + 1;
        }
        redisAlgorithmKernel.setLoopAccount(groupType.getId(), groupAct.getAccountId(), num);

        //构建输出对象
        return groupAct;
    }
}
